﻿#define _CRT_SECURE_NO_WARNINGS 1

#include "snake.h"

//GAMESTART

//控制台窗口大小的设置
//控制台窗口名字的设置
//鼠标光标的隐藏
//打印欢迎界面
//创建地图
//初始化蛇身
//创建第一个食物
void GameStart(pSnake ps)
{
	//0. 先设置窗口的大小，再光标隐藏
	system("mode con cols=100 lines=30");

	system("title 贪吃蛇");  //设置窗口标题
	HANDLE houtput = GetStdHandle(STD_OUTPUT_HANDLE);   //获得句柄

	//影藏光标操作
	CONSOLE_CURSOR_INFO CursorInfo;
	GetConsoleCursorInfo(houtput, &CursorInfo);//获取控制台光标信息
	CursorInfo.bVisible = false; //隐藏控制台光标
	SetConsoleCursorInfo(houtput, &CursorInfo);//设置控制台光标状态

	//1. 打印环境界面和功能介绍
	WelcomeToGame();
	//2. 绘制地图
	CreateMap();
	//3. 创建蛇
	InitSnake(ps);
	//4. 创建食物
	CreateFood(ps);
}

//位置函数
void SetPos(short x, short y)
{
	//获得标准输出设备的句柄
	HANDLE houtput = NULL;
	houtput = GetStdHandle(STD_OUTPUT_HANDLE);

	//定位光标的位置
	COORD pos = { x, y };
	SetConsoleCursorPosition(houtput, pos);
}

//1. 打印环境界面和功能介绍
void WelcomeToGame()
{
	SetPos(40, 14);
	wprintf(L"欢迎来到贪吃蛇小游戏\n");

	SetPos(42, 20);
	system("pause");    //按任意键继续......    按下后会切换页面
	system("cls");      //清空上一页面的窗口信息

	SetPos(25, 14);
	wprintf(L"用 ↑. ↓ . ← . → 来控制蛇的移动，按F3加速，F4减速\n");

	SetPos(25, 15);
	wprintf(L"加速能够得到更高的分数\n");

	SetPos(42, 20);
	system("pause");    //按任意键继续......    按下后会切换页面
	system("cls");		//清空上一页面的窗口信息
}

//2. 绘制地图
void CreateMap()
{
	//上
	int i = 0;
	SetPos(0, 0);	//上(0,0)-(56, 0)
	for (i = 0; i < 29; i++)
	{
		wprintf(L"%lc", WALL);
	}

	//下
	SetPos(0, 26);	//下(0,26)-(56, 26)
	for (i = 0; i < 29; i++)
	{
		wprintf(L"%lc", WALL);
	}
	//左
	for (i = 1; i <= 25; i++)
	{
		SetPos(0, i);	//x是0，y从1开始增⻓
		wprintf(L"%lc", WALL);
	}

	//右
	for (i = 1; i <= 25; i++)
	{
		SetPos(56, i);	//x是56，y从1开始增⻓
		wprintf(L"%lc", WALL);
	}
}

//3. 创建蛇
void InitSnake(pSnake ps)  //初始化蛇身需要将蛇的结构指针传给函数
{
	int i = 0;
	pSnakeNode cur = NULL;
	//创建蛇⾝节点，并初始化坐标
	//头插法
	for (i = 0; i < 5; i++)
	{
		cur = (pSnakeNode)malloc(sizeof(SnakeNode));  //为蛇身节点申请空间
		if (cur == NULL)		//防止空间申请失败
		{
			perror("InitSnake()::malloc()");
			return;
		}

		//给节点进行坐标定位
		cur->next = NULL;
		cur->x = POS_X + 2 * i;
		cur->y = POS_Y;

		//头插法插入链表
		if (ps->_pSnake == NULL) //空链表
		{
			ps->_pSnake = cur;
		}
		else //非空
		{
			cur->next = ps->_pSnake;
			ps->_pSnake = cur;
		}
	}

	//打印贪吃蛇
	cur = ps->_pSnake;  //将蛇头地址给cur
	while (cur)
	{
		SetPos(cur->x, cur->y);	//给定位置
		wprintf(L"%lc", BODY);
		cur = cur->next;
	}

	//设置贪吃蛇的属性
	ps->_dir = RIGHT;//默认向右
	ps->_score = 0;
	ps->_food_weight = 10;
	ps->_sleep_time = 200;//单位是毫秒，休眠时间是蛇每走一步中间间隔的时间
	ps->_status = OK;

}

//4. 创建食物
void CreateFood(pSnake ps)
{
	int x = 0;
	int y = 0;

	//生成x坐标需是2的倍数
	//x：2~54
	//y: 1~25

again:
	do
	{
		x = rand() % 53 + 2; 	//根据合适的坐标范围随机生成食物坐标
		y = rand() % 25 + 1;
	} while (x % 2 != 0);		//判断x坐标是否是2的倍数，不是就进入循环重新生成坐标

	//x和y的坐标不能和蛇的身体坐标冲突
	pSnakeNode cur = ps->_pSnake;	//将指向蛇头的指针赋给cur
	while (cur)
	{
		if (x == cur->x && y == cur->y)	//判断是否和身体坐标发生冲突
		{
			goto again;					//如果冲突，就返回到again标点重新生成坐标
		}
		cur = cur->next;
	}							//到这里随机生成的食物坐标就通过检查了

	//创建食物的节点
	pSnakeNode pFood = (pSnakeNode)malloc(sizeof(SnakeNode));  //为食物节点申请空间
	if (pFood == NULL)		//防止空间申请失败
	{
		perror("CreateFood()::malloc()");
		return;
	}

	//将生成的坐标赋给食物节点
	pFood->x = x;
	pFood->y = y;
	pFood->next = NULL;  //让食物节点的next指向空，达到食物节点独立。

	SetPos(x, y);//定位位置
	wprintf(L"%lc", FOOD);

	ps->_pFood = pFood;		//将食物信息赋给食物指针
}


//GAMERUN

//打印帮助信息
void PrintHelpInfo()
{
	SetPos(64, 5);
	wprintf(L"%ls", L"按空格开始游戏！");
	SetPos(64, 14);
	wprintf(L"%ls", L"不能穿墙，不能咬到自己");
	SetPos(64, 15);
	wprintf(L"%ls", L"用 ↑. ↓ . ← . → 来控制蛇的移动");
	SetPos(64, 16);
	wprintf(L"%ls", L"按F3加速，F4减速");
	SetPos(64, 17);
	wprintf(L"%ls", L"按ESC退出游戏，按空格暂停游戏");

	SetPos(64, 18);
	wprintf(L"%ls", L"版权归Jason所有");
}

//封装宏来检测按键状态——低位为1为按下，0为未按下
#define KEY_PRESS(VK) ((GetAsyncKeyState(VK)&0x1) ? 1 : 0)

//暂停函数
void Pause()
{
	while (1)
	{
		Sleep(200);					//暂停200毫秒
		if (KEY_PRESS(VK_SPACE))	//如果没有按下空格，则会反复暂停200毫秒
		{
			break;
		}
	}
}

//游戏运行
void GameRun(pSnake ps)
{
	//在窗口右侧打印帮助信息
	PrintHelpInfo();
	do
	{
		//打印总分数和食物的分值
		SetPos(64, 10);
		printf("总分数:%d\n", ps->_score);
		SetPos(64, 11);
		printf("当前食物的分数:%2d\n", ps->_food_weight);

		//检测按键是否被按下，以及防止按键冲突
		if (KEY_PRESS(VK_UP) && ps->_dir != DOWN)
		{
			ps->_dir = UP;
		}
		else if (KEY_PRESS(VK_DOWN) && ps->_dir != UP)
		{
			ps->_dir = DOWN;
		}
		else if (KEY_PRESS(VK_LEFT) && ps->_dir != RIGHT)
		{
			ps->_dir = LEFT;
		}
		else if (KEY_PRESS(VK_RIGHT) && ps->_dir != LEFT)
		{
			ps->_dir = RIGHT;
		}
		else if (KEY_PRESS(VK_SPACE))
		{
			Pause();   //暂停函数
		}
		else if (KEY_PRESS(VK_ESCAPE))
		{
			//正常退出游戏
			ps->_status = END_NORMAL;    //将游戏状态设为正常退出
		}
		else if (KEY_PRESS(VK_F3))
		{
			//加速
			if (ps->_sleep_time > 80)    //当休眠时间被缩短到80ms时，速度已经足够快了
			{
				ps->_sleep_time -= 30;   //减少休眠时间
				ps->_food_weight += 2;   //增加食物的分数，最高分是20分
			}
		}
		else if (KEY_PRESS(VK_F4))
		{
			//减速
			if (ps->_food_weight > 2)  //食物分数已经降到了最低标准
			{
				ps->_sleep_time += 30;  //增加休眠时间
				ps->_food_weight -= 2;  //减少食物的分数
			}
		}

		SnakeMove(ps);//蛇走一步的过程的函数

		Sleep(ps->_sleep_time);  //蛇每次到达一定状态，就要进行休眠

	} while (ps->_status == OK);   //只有游戏状态是ok，才会再次进入循环重复进行检测，使游戏进行下去。
}





//蛇身运动检测
void SnakeMove(pSnake ps)
{
	//创建一个结点，表示蛇即将到的下一个节点
	pSnakeNode pNextNode = (pSnakeNode)malloc(sizeof(SnakeNode));
	if (pNextNode == NULL)
	{
		perror("SnakeMove()::malloc()");
		return;
	}
	//确定下⼀个节点的坐标，下⼀个节点的坐标根据蛇头的坐标和方向确定
	switch (ps->_dir)	//选择方向
	{
	case UP:
		pNextNode->x = ps->_pSnake->x;
		pNextNode->y = ps->_pSnake->y - 1;
		break;
	case DOWN:
		pNextNode->x = ps->_pSnake->x;
		pNextNode->y = ps->_pSnake->y + 1;
		break;
	case LEFT:
		pNextNode->x = ps->_pSnake->x - 2;
		pNextNode->y = ps->_pSnake->y;
		break;
	case RIGHT:
		pNextNode->x = ps->_pSnake->x + 2;
		pNextNode->y = ps->_pSnake->y;
		break;
	}

	//检测下一个坐标处是否是食物
	if (NextIsFood(pNextNode, ps))
	{
		EatFood(pNextNode, ps);
	}
	else
	{
		NoFood(pNextNode, ps);
	}

	//检测蛇是否撞墙
	KillByWall(ps);
	//检测蛇是否撞到自己
	KillBySelf(ps);
}


//判断下一个坐标是否是食物
//pSnakeNode pn 是下⼀个节点的地址
//pSnake ps 贪吃蛇的指针
int NextIsFood(pSnakeNode pn, pSnake ps)
{
	return (ps->_pFood->x == pn->x && ps->_pFood->y == pn->y);
}

//下一个位置是食物，就吃掉食物
//pSnakeNode pn 是下⼀个节点的地址
//pSnake ps 贪吃蛇的指针
void EatFood(pSnakeNode pn, pSnake ps)
{
	//头插法
	ps->_pFood->next = ps->_pSnake;
	ps->_pSnake = ps->_pFood;

	//释放下一个位置的节点
	free(pn);
	pn = NULL;

	pSnakeNode cur = ps->_pSnake;
	//打印蛇
	while (cur)
	{
		SetPos(cur->x, cur->y);
		wprintf(L"%lc", BODY);
		cur = cur->next;
	}
	ps->_score += ps->_food_weight;  //增加食物获得总分

	//重新创建食物
	CreateFood(ps);
}


//下一个位置不是食物
void NoFood(pSnakeNode pn, pSnake ps)
{
	//头插法
	pn->next = ps->_pSnake;
	ps->_pSnake = pn;

	pSnakeNode cur = ps->_pSnake;
	while (cur->next->next != NULL)
	{
		SetPos(cur->x, cur->y);
		wprintf(L"%lc", BODY);
		cur = cur->next;
	}

	//把最后一个结点打印成空格
	SetPos(cur->next->x, cur->next->y);
	printf("  ");

	//释放最后一个结点
	free(cur->next);

	//把倒数第二个节点的地址置为NULL
	cur->next = NULL;
}

//检测蛇是否撞墙
void KillByWall(pSnake ps)
{		//如果蛇头坐标==墙的坐标
	if (ps->_pSnake->x == 0 || ps->_pSnake->x == 56 ||
		ps->_pSnake->y == 0 || ps->_pSnake->y == 26)
	{
		ps->_status = KILL_BY_WALL;    //将游戏状态改为：撞墙死
	}
}


//检测蛇是否撞到自己
void KillBySelf(pSnake ps)  //此函数和上边的撞墙函数同理
{
	pSnakeNode cur = ps->_pSnake->next;
	while (cur)
	{
		if (cur->x == ps->_pSnake->x && cur->y == ps->_pSnake->y)
		{
			ps->_status = KILL_BY_SELF;
			break;
		}
		cur = cur->next;    //通过函数遍历蛇身所有节点来检测蛇头是否撞到蛇身
	}
}


//游戏善后的工作
void GameEnd(pSnake ps)
{
	SetPos(24, 12);
	switch (ps->_status)    //根据最后游戏结束的不同状态打印信息
	{
	case END_NORMAL:
		wprintf(L"您主动结束游戏\n");
		break;
	case KILL_BY_WALL:
		wprintf(L"您撞到墙上，游戏结束\n");
		break;
	case KILL_BY_SELF:
		wprintf(L"您撞到了自己，游戏结束\n");
		break;
	}

	//释放蛇身的链表
	pSnakeNode cur = ps->_pSnake;
	while (cur)
	{
		pSnakeNode del = cur;
		cur = cur->next;
		free(del);
	}
}